﻿/*
 * This software is the confidential and proprietary
 * information of NetBay
 * Such confidential information should not be disclosed
 * NetBay copyright(c) 2010
 */

#region Using
using System;
using System.ComponentModel;
using System.Threading;
#endregion

namespace NetBay.Core.Silverlight.ComponentModel
{
    /// <summary>
    /// Gestion d'une tâche asynchrone dont le temps d'exécution est limité
    /// </summary>
    public class BackgroundWorkerTimeout
    {
        #region Events

        /// <summary>
        /// Levé lorsque la méthode RunWorkerAsync est appelée
        /// </summary>
        public event DoWorkEventHandler DoWork;

        #endregion

        #region Private Members

        /// <summary>
        /// BackgroundWorker interne
        /// </summary>
        private readonly BackgroundWorker _backgroundWorker;

        /// <summary>
        /// Objet d'attente de l'exécution de la tâche
        /// </summary>
        private ManualResetEvent _synchro;

        private Exception _exceptionFired;

        #endregion

        #region Public Members

        /// <summary>
        /// Obtient ou définit le temps d'exécution maximum de la tâche
        /// </summary>
        public uint TimeOut { get; private set; }

        #endregion

        #region Constructeur

        /// <summary>
        /// Constructeur
        /// </summary>
        public BackgroundWorkerTimeout()
        {
            _backgroundWorker = new BackgroundWorker();

            _backgroundWorker.DoWork += _backgroundWorker_DoWork;
            _backgroundWorker.RunWorkerCompleted += _backgroundWorker_RunWorkerCompleted;
        }

        /// <summary>
        /// Constructeur
        /// </summary>
        /// <param name="timeout">Temps maximum d'exécution de l'opération</param>
        public BackgroundWorkerTimeout(uint timeout)
            : this()
        {
            TimeOut = timeout;
        }

        #endregion

        #region Events Handlers

        /// <summary>
        /// Gestion de l'évènement DoWork du BackgroundWorker interne
        /// </summary>
        /// <param name="sender">BackgroundWorker</param>
        /// <param name="e">Paramètres de l'évènement</param>
        private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            if (DoWork != null)
                DoWork(this, e);
        }

        /// <summary>
        /// Gestion de l'évènement RunWorkerCompleted du BackgroundWorker interne
        /// </summary>
        /// <param name="sender">BackgroundWorker</param>
        /// <param name="e">Paramètres de l'évènement</param>
        private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            _exceptionFired = e.Result as Exception;
            _synchro.Set();
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Démarre l'éxecution de la tâche de fond
        /// </summary>
        public void RunWorkerAsync()
        {
            RunWorkerAsync(null);
        }

        /// <summary>
        /// Démarre l'éxecution de la tâche de fond
        /// </summary>
        /// <param name="argument">Paramètres de la tâche à exécuter</param>
        public void RunWorkerAsync(object argument)
        {
            _synchro = new ManualResetEvent(false);

            _exceptionFired = null;
            Thread thread = new Thread(() => Execute(argument))
                                {
                                    IsBackground = true
                                };
            thread.Start();

            int wait = WaitHandle.WaitAny(new WaitHandle[] { _synchro }, (int)TimeOut);

            if (_exceptionFired != null)
                throw _exceptionFired;
            if (wait == WaitHandle.WaitTimeout)
            {
                thread.Abort();
                string toDisplay = "argument is NULL";
                if (argument != null)
                    toDisplay = argument.ToString();
                throw new TimeoutException("Timeout(" + TimeOut / 1000 + ")s to execute(" + toDisplay + ")");
            }
        }

        private void Execute(object arg)
        {
            _backgroundWorker.RunWorkerAsync(arg);
        }

        #endregion
    }
}